﻿// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure.Core.TestFramework;
using NUnit.Framework;

namespace Azure.AI.FormRecognizer.DocumentAnalysis.Tests
{
    /// <summary>
    /// The suite of tests for miscellaneous operations in the <see cref="DocumentModelAdministrationClient"/> class.
    /// </summary>
    /// <remarks>
    /// These tests have a dependency on live Azure services and may incur costs for the associated
    /// Azure subscription.
    /// </remarks>
    public class MiscellaneousOperationsLiveTests : DocumentAnalysisLiveTestBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MiscellaneousOperationsLiveTests"/> class.
        /// </summary>
        /// <param name="isAsync">A flag used by the Azure Core Test Framework to differentiate between tests for asynchronous and synchronous methods.</param>
        public MiscellaneousOperationsLiveTests(bool isAsync, DocumentAnalysisClientOptions.ServiceVersion serviceVersion)
            : base(isAsync, serviceVersion)
        {
        }

        #region Resource Details

        [RecordedTest]
        [TestCase(true)]
        [TestCase(false)]
        public async Task GetResourceDetails(bool useTokenCredential)
        {
            var client = CreateDocumentModelAdministrationClient(useTokenCredential);
            var startTime = Recording.UtcNow;

            // Ensure we will have at least one custom model to populate the resource details.
            await using var disposableModel = await BuildDisposableDocumentModelAsync();

            ResourceDetails resourceDetails = await client.GetResourceDetailsAsync();

            Assert.Greater(resourceDetails.CustomDocumentModelCount, 0);
            Assert.GreaterOrEqual(resourceDetails.CustomDocumentModelLimit, resourceDetails.CustomDocumentModelCount);

            ResourceQuotaDetails neuralQuota = resourceDetails.NeuralDocumentModelQuota;

            if (_serviceVersion >= DocumentAnalysisClientOptions.ServiceVersion.V2023_07_31)
            {
                Assert.GreaterOrEqual(neuralQuota.Used, 0);
                Assert.GreaterOrEqual(neuralQuota.Quota, neuralQuota.Used);
                Assert.Greater(neuralQuota.QuotaResetsOn, startTime);
            }
            else
            {
                Assert.Null(neuralQuota);
            }
        }

        #endregion

        #region Get Operation

        [RecordedTest]
        public async Task GetOperationCanAuthenticateWithTokenCredential()
        {
            var client = CreateDocumentModelAdministrationClient();
            await using var disposableModel = await BuildDisposableDocumentModelAsync();

            OperationDetails operationDetails = await client.GetOperationAsync(disposableModel.Operation.Id);

            // Sanity check to make sure we got an actual response back from the service.
            Assert.AreEqual(disposableModel.Operation.Id, operationDetails.OperationId);
        }

        [RecordedTest]
        public async Task GetOperationWithDocumentModelBuild()
        {
            var client = CreateDocumentModelAdministrationClient();
            var startTime = Recording.UtcNow;
            var options = new BuildDocumentModelOptions()
            {
                Description = $"This model was generated by a .NET test.",
                Tags = { { "tag1", "value1" }, { "tag2", "value2" } }
            };
            await using var disposableModel = await BuildDisposableDocumentModelAsync(options: options);

            OperationDetails operationDetails = await client.GetOperationAsync(disposableModel.Operation.Id);

            // The endpoint environment variable may or may not contain a trailing '/' character. Trim the string
            // to ensure the behavior is consistent.

            var trimmedEndpoint = TestEnvironment.Endpoint.Trim('/');
            var resourceLocation = $"{trimmedEndpoint}/formrecognizer/documentModels/{disposableModel.ModelId}?api-version={ServiceVersionString}";

            ValidateOperationDetails(operationDetails, disposableModel.Operation.Id,
                DocumentOperationKind.DocumentModelBuild, resourceLocation, startTime, options.Tags);

            var buildOperationDetails = operationDetails as DocumentModelBuildOperationDetails;

            Assert.IsNotNull(buildOperationDetails);

            DocumentAssert.AreEqual(disposableModel.Value, buildOperationDetails.Result);
        }

        [RecordedTest]
        [ServiceVersion(Min = DocumentAnalysisClientOptions.ServiceVersion.V2023_07_31)]
        public async Task GetOperationWithDocumentClassifierBuild()
        {
            var client = CreateDocumentModelAdministrationClient();
            var classifierId = Recording.GenerateId();
            var description = "This model was generated by a .NET test.";
            var startTime = Recording.UtcNow;
            var tags = new Dictionary<string, string>();
            await using var disposableClassifier = await BuildDisposableDocumentClassifierAsync(classifierId, description);

            OperationDetails operationDetails = await client.GetOperationAsync(disposableClassifier.Operation.Id);

            // The endpoint environment variable may or may not contain a trailing '/' character. Trim the string
            // to ensure the behavior is consistent.

            var trimmedEndpoint = TestEnvironment.Endpoint.Trim('/');
            var resourceLocation = $"{trimmedEndpoint}/formrecognizer/documentClassifiers/{disposableClassifier.ClassifierId}?api-version={ServiceVersionString}";

            ValidateOperationDetails(operationDetails, disposableClassifier.Operation.Id,
                DocumentOperationKind.DocumentClassifierBuild, resourceLocation, startTime, tags);

            var buildOperationDetails = operationDetails as DocumentClassifierBuildOperationDetails;

            Assert.IsNotNull(buildOperationDetails);

            DocumentAssert.AreEqual(disposableClassifier.Value, buildOperationDetails.Result);
        }

        [RecordedTest]
        public void GetOperationCanParseError()
        {
            var client = CreateDocumentModelAdministrationClient();
            var fakeId = "00000000-0000-0000-0000-000000000000";

            RequestFailedException ex = Assert.ThrowsAsync<RequestFailedException>(() => client.GetOperationAsync(fakeId));
            Assert.AreEqual("NotFound", ex.ErrorCode);
        }

        #endregion

        #region List Operations

        [RecordedTest]
        [TestCase(true)]
        [TestCase(false)]
        public async Task GetOperations(bool useTokenCredential)
        {
            var client = CreateDocumentModelAdministrationClient(useTokenCredential);
            var options = new BuildDocumentModelOptions()
            {
                Description = $"This model was generated by a .NET test.",
                Tags = { { "tag1", "value1" }, { "tag2", "value2" } }
            };

            // Make the models slightly different to make sure the cache won't return copies of the same model.
            await using var disposableModel0 = await BuildDisposableDocumentModelAsync(ContainerType.Singleforms, options);
            await using var disposableModel1 = await BuildDisposableDocumentModelAsync(ContainerType.MultipageFiles, options);

            OperationDetails operationDetails0 = await client.GetOperationAsync(disposableModel0.Operation.Id);
            OperationDetails operationDetails1 = await client.GetOperationAsync(disposableModel1.Operation.Id);

            var idMapping = new Dictionary<string, OperationSummary>();
            var expectedIdMapping = new Dictionary<string, OperationDetails>()
            {
                { disposableModel0.Operation.Id, operationDetails0 },
                { disposableModel1.Operation.Id, operationDetails1 }
            };

            await foreach (OperationSummary operationSummary in client.GetOperationsAsync())
            {
                if (expectedIdMapping.ContainsKey(operationSummary.OperationId))
                {
                    idMapping.Add(operationSummary.OperationId, operationSummary);
                }

                if (idMapping.Count == expectedIdMapping.Count)
                {
                    break;
                }
            }

            foreach (string id in expectedIdMapping.Keys)
            {
                Assert.True(idMapping.ContainsKey(id));

                OperationSummary operationSummary = idMapping[id];
                OperationDetails expected = expectedIdMapping[id];

                Assert.AreEqual(expected.OperationId, operationSummary.OperationId);
                Assert.AreEqual(expected.ServiceVersion, operationSummary.ServiceVersion);
                Assert.AreEqual(expected.Status, operationSummary.Status);
                Assert.AreEqual(expected.Kind, operationSummary.Kind);
                Assert.AreEqual(expected.PercentCompleted, operationSummary.PercentCompleted);
                Assert.AreEqual(expected.ResourceLocation.AbsoluteUri, operationSummary.ResourceLocation.AbsoluteUri);
                Assert.AreEqual(expected.CreatedOn, operationSummary.CreatedOn);
                Assert.AreEqual(expected.LastUpdatedOn, operationSummary.LastUpdatedOn);

                CollectionAssert.AreEquivalent(expected.Tags, operationSummary.Tags);
            }
        }

        #endregion

        private void ValidateOperationDetails(OperationDetails operationDetails, string id, DocumentOperationKind kind, string resourceLocation, DateTimeOffset startTime, IDictionary<string, string> tags)
        {
            Assert.AreEqual(id, operationDetails.OperationId);
            Assert.AreEqual(DocumentOperationStatus.Succeeded, operationDetails.Status);
            Assert.AreEqual(kind, operationDetails.Kind);
            Assert.AreEqual(100, operationDetails.PercentCompleted);
            Assert.AreEqual(resourceLocation, operationDetails.ResourceLocation.AbsoluteUri);
            Assert.Null(operationDetails.ServiceVersion);
            Assert.Null(operationDetails.Error);

            // Add a 4-hour tolerance because model could have been cached before this test.
            Assert.Greater(operationDetails.CreatedOn, startTime - TimeSpan.FromHours(4));
            Assert.Greater(operationDetails.LastUpdatedOn, operationDetails.CreatedOn);

            CollectionAssert.AreEquivalent(tags, operationDetails.Tags);
        }
    }
}
